<!doctype html>
<html>
    <head>
        <title>HT for Web 数据模型手册</title>
        <meta charset="UTF-8">    
        <meta name="viewport" content="user-scalable=yes, width=1024">
        <style type="text/css"> 
            h1, h2, h3, h4, h5, h6, p, blockquote {
                margin: 0;
                padding: 0;
            }
            body {
                font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
                font-size: 13px;
                line-height: 18px;
                color: #737373;
                background-color: white; 
                margin: 10px 13px 10px 13px;
            }
            table {
                margin: 10px 0 15px 0;
                border-collapse: collapse;
            }
            td,th {	
                border: 1px solid #ddd;
                padding: 3px 10px;
            }
            th {
                padding: 5px 10px;	
            }
            a {
                color: #0069d6;
            }
            a:hover {
                color: #0050a3;
                text-decoration: none;
            }
            a img {
                border: none;
            }
            p {
                margin-bottom: 9px;
            }
            h1, h2, h3, h4, h5, h6 {
                color: #404040;
                line-height: 36px;
            }
            h1 {
                margin-bottom: 18px;
                font-size: 30px;
            }
            h2 {
                font-size: 24px;
            }
            h3 {
                font-size: 18px;
            }
            h4 {
                font-size: 16px;
            }
            h5 {
                font-size: 14px;
            }
            h6 {
                font-size: 13px;
            }
            hr {
                margin: 0 0 19px;
                border: 0;
                border-bottom: 1px solid #ccc;
            }
            blockquote {
                padding: 13px 13px 21px 15px;
                margin-bottom: 18px;
                font-family:georgia,serif;
                font-style: italic;
            }
            blockquote:before {
                content:"\201C";
                font-size:40px;
                margin-left:-10px;
                font-family:georgia,serif;
                color:#eee;
            }
            blockquote p {
                font-size: 14px;
                font-weight: 300;
                line-height: 18px;
                margin-bottom: 0;
                font-style: italic;
            }
            code, pre {
                font-family: Monaco, Andale Mono, Courier New, monospace;
            }
            code {
                background-color: #fee9cc;
                color: rgba(0, 0, 0, 0.75);
                padding: 1px 3px;
                font-size: 12px;
                -webkit-border-radius: 3px;
                -moz-border-radius: 3px;
                border-radius: 3px;
            }
            pre {
                display: block;
                padding: 14px;
                margin: 0 0 18px;
                line-height: 16px;
                font-size: 11px;
                border: 1px solid #d9d9d9;
                white-space: pre-wrap;
                word-wrap: break-word;
            }
            pre code {
                background-color: #fff;
                color:#737373;
                font-size: 11px;
                padding: 0;
            }
            sup {
                font-size: 0.83em;
                vertical-align: super;
                line-height: 0;
            }
            * {
                -webkit-print-color-adjust: exact;
            }
            @media screen and (min-width: 914px) {
                body {
                    width: 854px;
                    margin:10px auto;
                }
            }
            @media print {
                body,code,pre code,h1,h2,h3,h4,h5,h6 {
                    color: black;
                }
                table, pre {
                    page-break-inside: avoid;
                }
            }                        
            iframe{
                width: 100%;
                border: 1px solid #34495E;
                margin: 0;                
            }            
            .logo{
                vertical-align: middle;
            }

        </style>        
        <script>
            function init() {
                var logoSrc = '',
                    logos = document.querySelectorAll('.logo'),
                    i = 0;
                for (; i < logos.length; i++) {
                    logos[i].src = logoSrc;
                }
                
                var iframes = document.querySelectorAll('iframe'),
                    func = function(){};
                for (i=0; i < iframes.length; i++) {
                    var iframe = iframes[i];
                    
                    // a small hack to make it work on android
                    iframe.ontouchstart = func;
                    
                    var div = document.createElement('div');
                    div.innerHTML = '<a href="' + iframe.src + '" target="_blank">独立运行→</a>';
                    iframe.parentNode.insertBefore(div, iframe);
                }   
            }
        </script>    
    </head>
    <body onload="init();">
        <a href="http://www.hightopo.com"><img class="logo"></a>HT for Web 数据模型手册
        <hr style="margin: 1px 0 20px 0">
<p>索引</p>

<ul><li><a href="#ref_designpattern">设计模式</a><ul><li><a href="#ref_mvc">MVC</a></li><li><a href="#ref_mvp">MVP</a></li><li><a href="#ref_mvvm">MVVM</a></li><li><a href="#ref_ovm">OVM</a></li></ul></li><li><a href="#ref_data">数据类型</a></li><li><a href="#ref_datamodel">数据容器</a></li><li><a href="#ref_selectionmodel">选择模型</a></li></ul>

<hr/>

<div id="ref_designpattern"></div>

<h2>设计模式</h2>

<p>正确使用合适的设计模式可极大提高框架的可维护性和可扩展性，良好的设计接口可极大减少用户代码量并提高可读性，
有利于大规模项目开发的团队分工，甚至不需要接入后台数据即可完成对前端组件的单元测试，
有助于组件和模型的<a href="http://en.wikipedia.org/wiki/Data_binding">数据绑定</a>和数据同步，
进而有助于开发厂商进一步提供所见即所得的可视化开发工具，
如 <a href="http://www.adobe.com/products/flash-builder.html">Adobe Flash Builder</a> 和 
<a href="http://en.wikipedia.org/wiki/VisualStudio">Microsoft Visual Studio</a>。</p>

<p><img src=""></p>

<div id="ref_mvc"></div>

<h3>MVC</h3>

<p>在早期的<code>GUI</code>设计领域，<a href="http://en.wikipedia.org/wiki/Swing_(Java)">Java/Swing</a>，<a href="http://en.wikipedia.org/wiki/JFace">SWT/JFace</a>，
<a href="http://developer.apple.com/library/ios/#documentation/general/conceptual/devpedia-cocoacore/MVC.html">Cocoa</a> 和 <a href="http://en.wikipedia.org/wiki/Qt_(framework)">Qt</a> 
都是基于 <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">Model View Controller (MVC)</a> 设计模式的典范。</p>

<div id="ref_mvp"></div>

<h3>MVP</h3>

<p><code>MVC</code>之后慢慢发展衍生的 <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter">Model View Presenter (MVP)</a> 设计模式逐渐被新的<code>GUI</code>框架所采用，
如基于<code>Flash</code>的 <a href="http://en.wikipedia.org/wiki/Apache_Flex">Apache/Flex</a> 企业应用组件，以及在<code>Swing</code>基础上封装的 <a href="http://www.jgoodies.com/">JGoodies</a> 框架。
 <code>Martin Fowler</code> 的 <a href="http://martinfowler.com/eaaDev/uiArchs.html">GUI Architectures</a> 对这两种设计模式进行了分析比较。</p>

<div id="ref_mvvm"></div>

<h3>MVVM</h3>

<p>近些年在<code>MVP</code>基础上再增加<code>ViewModel</code>层衍生的 <a href="http://en.wikipedia.org/wiki/MVVM">Model View ViewModel (MVVM)</a> 设计模式，
也被微软新一代的<code>GUI</code>开发框架 <a href="http://en.wikipedia.org/wiki/Windows_Presentation_Foundation">Silverlight/WPF</a> 采用，
包括像 <a href="http://knockoutjs.com/">Knockout</a> 这种为简化前端<code>HTML</code>组件和<code>js</code>数据绑定的框架也是基于<code>MVVM</code>设计模式。</p>

<div id="ref_ovm"></div>

<h3>OVM</h3>

<p><code>MVC/MVP/MVVM</code>等设计模式的提出，方便了大家对不同框架的理解和归类，任何<code>GUI</code>框架在具体实现上都会有自己的演变和特性，
<code>HT</code>的整体框架更类似<code>MVP</code>和<code>MVVM</code>的设计模式，但我们更倾向于称<code>HT</code>的设计模式为 <code>Object View Mapping (OVM)</code>，
类比于 <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">Object Relational Mapping (ORM)</a>，
通过面对对象方式的封装，屏蔽了各种视图组件的异构性，采用了统一的 <a href="#ref_datamodel">DataModel</a> 数据模型和
 <a href="#ref_selectionmodel">SelectionModel</a> 选择模型，可驱动 <code>ListView</code>，<code>TreeView</code>，<code>TableView</code>，
<code>TreeTableView</code>、<code>GraphView</code> 和 <code>Graph3dView</code> 等所有<code>HT</code>的视图组件。</p>

<blockquote><p><code>HT</code>这样的设计架构，用户仅需掌握统一的数据驱动接口，不会因视图组件增多带来额外学习成本，这是<code>HT</code>易上手易精通的根本。</p></blockquote>

<div id="ref_data"></div>

<h2>数据类型</h2>

<p><code>Data</code>类型贯穿整个<code>HT</code>框架，是最基础数据类型。</p>

<ul><li><code>getId()</code>和<code>setId(id)</code>获取和设置唯一编号，系统会自动分配，设置需注意<a href="#ref_datamodel">DataModel说明</a>，<code>DataModel#getDataById(id)</code>可查找</li><li><code>getTag()</code>和<code>setTag(tag)</code>获取和设置标示号，通过<code>DataModel#getDataByTag(tag)</code>可查找</li><li><code>getName()</code>和<code>setName(name)</code>获取和设置名称</li><li><code>getIcon()</code>和<code>setIcon(icon)</code>获取和设置小图标，常作为<code>TreeView</code>和<code>ListView</code>等组件上的节点小图标</li><li><code>getDisplayName()</code>和<code>setDisplayName(displayName)</code>获取和设置显示名称，常作为<code>Column</code>和<code>Property</code>的列头和属性名称显示</li><li><code>getToolTip()</code>和<code>setToolTip(tooltip)</code>获取和设置组件上该节点或图元的文字提示信息 </li><li><code>getParent()</code>和<code>setParent(parent)</code>获取和设置父亲节点，作为树层次结构的信息，内部会自动调用<code>addChild</code>或<code>removeChild</code></li><li><code>addChild(child, index)</code>添加孩子节点，<code>index</code>为孩子插入索引，为空则插入作为最后的孩子，内部会自动调用<code>setParent</code></li><li><code>removeChild(child)</code>删除指定孩子节点，内部会自动调用<code>setParent</code></li><li><code>clearChildren()</code>删除所有孩子节点，内部会自动调用<code>setParent</code></li><li><code>onChildAdded(child, index)</code>添加孩子时的回调函数，可重载做后续处理</li><li><code>onChildRemoved(child, index)</code>删除孩子时的回调函数，可重载做后续处理</li><li><code>onParentChanged(oldParent, parent)</code>改变父亲节点时的回调函数，可重载做后续处理    </li><li><code>size()</code>返回孩子总数</li><li><code>hasChildren()</code>判断是否有孩子，有则返回<code>true</code>，无则返回<code>false</code></li><li><code>isEmpty()</code>判断是否有孩子，有则返回<code>false</code>，无则返回<code>true</code></li><li><code>getChildren()</code>获取所有孩子节点，该函数返回内部<code>ht.List</code>类型数组对象引用</li><li><code>toChildren(matchFunc, scope)</code>根据<code>matchFunc</code>函数逻辑构建所有匹配图元的新<code>ht.List</code>类型数组对象</li><li><code>eachChild(func, scope)</code>遍历所有孩子，可指定函数<code>scope</code>域</li><li><code>getChildAt(index)</code>返回指定索引位置孩子    </li><li><code>isParentOf(data)</code>判断本图元是否为指定<code>data</code>的父亲图元</li><li><code>isDescendantOf(data)</code>判断本图元是否为指定<code>data</code>图元的子孙          </li><li><code>isRelatedTo(data)</code>判断本图元与指定<code>data</code>图元是否有父子或子孙关系</li><li><code>layer</code>属性通过<code>getLayer()</code>和<code>setLayer(layer)</code>操作，对应图元在<code>GraphView</code>组件中的图层位置，默认值为空</li><li><code>isAdjustChildrenToTop()</code>和<code>setAdjustChildrenToTop(true)</code>，默认为<code>false</code>，<code>ht.Node</code>类型默认为<code>true</code>，
<code>GraphView</code>默认点击图元会自动<code>sendToTop</code>，该属性决定是否对子图元也进行<code>sendToTop</code>操作</li><li><code>firePropertyChange(property, oldValue, newValue)</code>派发属性变化事件，可使用<code>fp</code>的简写方式</li><li><code>onPropertyChanged(event)</code>属性变化回调函数，可重载做后续处理</li><li><code>invalidate()</code>该函数用户强制触发属性变化事件通知界面更新，内部实现为<code>this.fp(&#39;*&#39;, false, true)</code></li><li><code>getStyleMap()</code>返回图元内部样式映射信息，<code>getStyle(name)</code>时如果<code>styleMap</code>对应值为空，自动会返回<code>ht.Style</code>定义的信息</li><li><code>getStyle(&#39;name&#39;)</code>和<code>setStyle(&#39;name&#39;, value)</code>获取和设置图元样式，可采用<code>s(name/name,value/json)</code>的简写方式</li><li><code>onStyleChanged(name, oldValue, newValue)</code>当<code>style</code>属性变化时会回调该函数，可重载做后续处理 </li><li><code>getAttrObject()</code>和<code>setAttrObject(obj)</code>获取和设置<code>attr</code>属性对象，该属性默认为空，用于存储用户业务信息</li><li><code>getAttr(name)</code>和<code>setAttr(name, value)</code>获取和设置<code>attr</code>对象的属性，可采用<code>a(name/name,value/json)</code>的简写方式</li><li><code>onAttrChanged(name, oldValue, newValue)</code>当<code>attr</code>属性变化时会回调该函数，可重载做后续处理 </li><li><code>toLabel()</code>返回值默认作为<code>TreeView</code>和<code>GraphView</code>等组件上的图元文字标签，默认返回<code>displayName||name</code>信息</li><li><code>addStyleIcon(name, icons)</code>和<code>removeStyleIcon(name)</code>增加和删除<code>style</code>中<code>icons</code>属性，可参考<a href="#ref_styleicon">icon章节</a></li><li><code>getSerializableProperties()</code>返回需要序列化的属性名称<code>map</code>，参见<a href="../serialization/ht-serialization-guide.html">序列化手册</a></li><li><code>getSerializableStyles()</code>返回需要序列化的<code>style</code>属性名称<code>map</code>，参见<a href="../serialization/ht-serialization-guide.html">序列化手册</a></li><li><code>getSerializableAttrs()</code>返回需要序列化的<code>attr</code>属性名称<code>map</code>，参见<a href="../serialization/ht-serialization-guide.html">序列化手册</a></li></ul>

<p><iframe src="examples/example_data.html" style="height:160px"></iframe></p>

<pre><code>ht.Default.setImage(&#39;edit&#39;, &#39;res/edit.png&#39;);
ht.Default.setImage(&#39;mail&#39;, &#39;res/mail.png&#39;);
ht.Default.setImage(&#39;readmail&#39;, &#39;res/readmail.png&#39;);
ht.Default.setImage(&#39;search&#39;, &#39;res/search.png&#39;);
ht.Default.setImage(&#39;settings&#39;, &#39;res/settings.png&#39;);            

function init(){                                
    dataModel = new ht.DataModel();                    
    treeView = new ht.widget.TreeView(dataModel);
    view = treeView.getView();            

    view.className = &#39;main&#39;;
    document.body.appendChild(view);    
    window.addEventListener(&#39;resize&#39;, function (e) {
        treeView.invalidate();
    }, false);                         

    var inbox = addData(&#39;Inbox&#39;, &#39;mail&#39;);
    addData(&#39;Read Mail&#39;, &#39;readmail&#39;, inbox);                             
    addData(&#39;Drafts&#39;, &#39;edit&#39;);
    var search = addData(&#39;Search Folders&#39;, &#39;search&#39;);
    addData(&#39;Categorized Mail&#39;, &#39;search&#39;, search);                             
    addData(&#39;Large mail&#39;, &#39;search&#39;, search);                             
    addData(&#39;UnRead Mail&#39;, &#39;search&#39;, search);                             
    addData(&#39;Settings&#39;, &#39;settings&#39;);

    treeView.expandAll();
    treeView.getSelectionModel().setSelection(search);
}

function addData(name, icon, parent){
    var data = new ht.Data();
    data.setName(name);
    data.setIcon(icon);
    data.setParent(parent); // or parent.addChild(data);
    dataModel.add(data);                
    return data;
}</code></pre>

<div id="ref_datamodel"></div>

<h2>数据容器</h2>

<p>数据容器<code>ht.DataModel</code>(以下简称<code>DataModel</code>)作为承载<code>Data</code>数据的模型，管理着<code>Data</code>数据的增删以及变化事件派发，
<code>HT</code>框架所有组件都是通过绑定<code>DataModel</code>，以不同的形式呈现到用户界面；同时组件也会监听<code>DataModel</code>模型的变化事件，
实时同步更新界面数据信息，掌握了<code>DataModel</code>的操作就掌握了所有组件的模型驱动方式。</p>

<p><code>Data</code>类型对象构造时内部会自动被赋予一个<code>id</code>属性，可通过<code>data.getId()</code>和<code>data.setId(id)</code>获取和设置，
<code>Data</code>对象添加到<code>DataModel</code>之后不允许修改<code>id</code>值，可通过<code>dataModel.getDataById(id)</code>快速查找<code>Data</code>对象。</p>

<p>一般建议<code>id</code>属性由<code>HT</code>自动分配，用户业务意义的唯一标示可存在<code>tag</code>属性上，通过<code>Data#setTag(tag)</code>函数允许任意动态改变<code>tag</code>值，
通过<code>DataModel#getDataByTag(tag)</code>可查找到对应的<code>Data</code>对象，并支持通过<code>DataModel#removeDataByTag(tag)</code>删除<code>Data</code>对象。</p>

<p><code>id</code>和<code>tag</code>的方式都是针对唯一标示的<code>Data</code>对象，若搜索非唯一属性可采用<a href="../../plugin/quickfinder/ht-quickfinder-guide.html">ht.QuickFinder插件</a>。</p>

<p>使用<code>DataModel</code>时需要特别注意：一般要求有父子关系的<code>Data</code>都应逐一加入容器。常遇到<code>parent</code>加入容器，但<code>children</code>未加入，
导致组件看不到<code>children</code>的问题，另外添加<code>parent</code>并不会自动加载所有子孙，这点务必注意。</p>

<p><code>Data</code>类型有<code>getDataModel()</code>函数，当<code>Data</code>加入容器后<code>data.getDataModel()</code>能获得当前所在容器信息，
不允许一个<code>Data</code>对象同时加入多个<code>DataModel</code>容器中。</p>

<ul><li><code>add(data, index)</code>添加<code>Data</code>对象，<code>index</code>一般无需指定，其只在<code>data</code>的<code>parent</code>为空时才起作用，指定插入<code>roots</code>数组的索引位置</li><li><code>remove(data)</code>删除<code>Data</code>对象，该操作有以下副作用：<ul><li>其子孙被递归从<code>DataModel</code>中删除</li><li>被断开父子关系<code>data.setParent(null)</code></li><li><code>Edge</code>类型通过<code>edge.setSource(null)</code>和<code>data.setTarget(null)</code>断开节点关系</li><li><code>Node</code>类型会将其关联的连线从<code>DataModel</code>中删除</li><li><code>Node</code>类型通过<code>data.setHost(null)</code>断开与宿主吸附节点关系</li></ul></li><li><code>clear()</code>删除容器中所有<code>Data</code>对象，该操作一次性清空，没有逐个<code>remove</code>的过程，不会影响<code>Data</code>父子关系</li><li><code>onAdded(data)</code>图元添加时回调函数，可重载做后续处理</li><li><code>onRemoved(data)</code>图元删除时回调函数，可重载做后续处理</li><li><code>contains(data)</code>判断容器是否包含该<code>data</code>对象</li><li><code>size()</code>返回当前容器中<code>Data</code>对象的总数</li><li><code>isEmpty()</code>判断容器是否为空</li><li><code>getRoots()</code>返回所有<code>parent</code>为空的<code>Data</code>对象</li><li><code>getDataById(id)</code>返回指定<code>id</code>的<code>Data</code>对象</li><li><code>removeDataById(id)</code>删除指定<code>id</code>的<code>Data</code>对象</li><li><code>getDataByTag(tag)</code>返回指定<code>tag</code>标示的<code>Data</code>对象</li><li><code>removeDataByTag(tag)</code>删除指定<code>tag</code>标示的<code>Data</code>对象</li><li><code>each(func, scope)</code>遍历所有<code>Data</code>对象</li><li><code>eachByDepthFirst(func, data, scope)</code>以<code>data</code>为起始深度优先遍历<code>Data</code>对象</li><li><code>eachByBreadthFirst(func, data, scope)</code>以<code>data</code>为起始广度优先遍历<code>Data</code>对象</li><li><code>getDatas()</code>返回所有添加到容器的<code>Data</code>数据<code>ht.List</code>数组</li><li><code>toDatas(mathchFunc, scope)</code>返回筛选后的新<code>ht.List</code>对象数组，第一参数为空相当于复制全部对象数组</li><li><code>getSelectionModel()</code>获取该容器的选择模型，可用简写<code>sm()</code></li><li><code>addDataModelChangeListener(function(e){}, scope)</code>增加数据模型增删变化事件监听器，可用简写<code>mm(func, scope)</code><ul><li><code>e.kind === &#39;add&#39;</code>代表添加<code>Data</code>对象，<code>e.data</code>为被添加的对象</li><li><code>e.kind === &#39;remove&#39;</code>代表删除<code>Data</code>对象，<code>e.data</code>为被删除的对象</li><li><code>e.kind === &#39;clear&#39;</code>代表容器被清除</li></ul></li><li><code>removeDataModelChangeListener(func, scope)</code>删除数据模型增删变化事件监听器，可用简写<code>umm(func, scope)</code></li><li><code>addDataPropertyChangeListener(function(e){}, scope)</code>增加模型中<code>Data</code>数据属性变化事件监听器，可用简写<code>md(func, scope)</code><ul><li><code>e.data</code>代表属性变化的对象</li><li><code>e.property</code>代表变化属性的名字</li><li><code>e.newValue</code>代表属性的新值</li><li><code>e.oldValue</code>代表属性的老值</li><li><code>Data</code>对象在设置属性值函数内调用<code>firePropertyChange(property, oldValue, newValue)</code>触发属性变化事件：<ul><li><code>get/set</code>类型属性，如<code>setAge(98)</code>触发事件的<code>e.property</code>为<code>age</code></li><li><code>style</code>类型属性名前加<code>s:</code>前缀以区分，如<code>setStyle(&#39;age&#39;, 98)</code>触发事件的<code>e.property</code>为<code>s:age</code></li><li><code>attr</code>类型属性名前加<code>a:</code>前缀以区分，如<code>setAttr(&#39;age&#39;, 98)</code>触发事件的<code>e.property</code>为<code>a:age</code> </li></ul></li></ul></li><li><code>removeDataPropertyChangeListener(func, scope)</code>删除模型中<code>Data</code>数据属性变化事件监听器，可用简写<code>umd(func, scope)</code></li><li><code>onDataPropertyChanged(data, e)</code>图元属性变化回调函数，可重载做后续处理</li><li><code>getSiblings(data)</code>获取和<code>data</code>同父子层次的兄弟数组，如果data父亲为空，则返回<code>dataModel.getRoots()</code>                </li><li><code>moveTo(data, newIndex)</code>移动图元到同层兄弟数组中得指定索引</li><li><code>moveUp(data)</code>移动图元到同层兄弟数组中的上一个位置</li><li><code>moveDown(data)</code>移动图元到同层兄弟数组中的下一个位置</li><li><code>moveToTop(data)</code>移动图元到同层兄弟数组的顶部</li><li><code>moveToBottom(data)</code>移动图元到同层兄弟数组的底部</li><li><code>moveSelectionUp(sm)</code>移动当前选中图元到同层兄弟数组中的上一个位置，<code>sm</code>为空则采用<code>DataModel</code>绑定的选中模型</li><li><code>moveSelectionDown(sm)</code>移动当前选中图元到同层兄弟数组中的下一个位置，<code>sm</code>为空则采用<code>DataModel</code>绑定的选中模型</li><li><code>moveSelectionToTop(sm)</code>移动当前选中图元到同层兄弟数组的顶部，<code>sm</code>为空则采用<code>DataModel</code>绑定的选中模型</li><li><code>moveSelectionToBottom(sm)</code>移动当前选中图元到同层兄弟数组的底部，<code>sm</code>为空则采用<code>DataModel</code>绑定的选中模型</li><li><code>serialize(space)</code>将数据模型序列化成<code>JSON</code>格式字符串，<code>space</code>为缩进空格数</li><li><code>toJSON</code>将数据模型序列化成<code>JSON</code>格式对象</li><li><code>deserialize(json, rootParent, setId)</code>反序列化数据到数据模型<ul><li><code>json</code>数据信息对象，用于解析生成对应的<code>Data</code>对象并添加到数据容器</li><li><code>rootParent</code>父节点对象，如果不为空，则反序列化的对象若无父亲者，设置<code>rootParent</code>为其父亲</li><li><code>setId</code>指定反序列化时，是否设置<code>json</code>信息上的<code>id</code>值</li></ul></li></ul>

<blockquote><p>通过下面<code>firePropertyChange</code>的代码片段可以知道，<code>oldValue</code>和<code>newValue</code>相同时属性变化事件不会派发，
属性变化事件通过<code>handleDataPropertyChange</code>传递给<code>DataModel</code>继续做处理，
后续处理包括继续派发事件给通过<code>addDataPropertyChangeListener</code>添加到<code>DataModel</code>的属性变化监听器。</p></blockquote>

<pre><code>firePropertyChange: function (property, oldValue, newValue) {
    if (oldValue === newValue) {
        return false;
    }
    var e = {
        property: property,
        oldValue: oldValue,
        newValue: newValue,
        data: this
    };
    if (this._dataModel) {
        this._dataModel.handleDataPropertyChange(e);
    }
    this.onPropertyChanged(e);
    return true;
} </code></pre>

<div id="ref_selectionmodel"></div>

<h2>选择模型</h2>

<p><code>ht.SelectionModel</code>管理<code>DataModel</code>模型中<code>Data</code>对象的选择状态，
每个<code>DataModel</code>对象都内置一个<code>SelectionModel</code>选择模型，控制这个<code>SelectionModel</code>即可控制所有绑定该<code>DataModel</code>的组件的对象选择状态，
这意味着共享同一<code>DataModel</code>的组件默认就具有选中联动功能。</p>

<p>如果希望某些组件不与其他组件选中联动，可通过调用<code>view.setSelectionModelShared(false)</code>，
这样该<code>view</code>将创建一个专属的<code>SelectionModel</code>实例。</p>

<p>综上所述有两种途径可得到<code>SelectionModel</code>：</p>

<ul><li><code>dataModel.getSelectionModel()</code>获取数据容器中组件共享的选中模型。</li><li><code>view.getSelectionModel()</code>获取当前组件使用的选中模型，<code>selectionModelShared</code>为<code>false</code>时，返回<code>view</code>专用的选择模型。</li></ul>

<p><code>SelectionModel</code>常用函数如下：</p>

<ul><li><code>getSelectionMode()</code>和<code>setSelectionMode(selectionMode)</code>获取和设置选中模式<ul><li><code>none</code>：不可选中。</li><li><code>single</code>：只可单选。</li><li><code>multiple</code>：默认值，允许多选。</li></ul></li><li><code>getFilterFunc()</code>和<code>setFilterFunc(func)</code>设置过滤器自定义可选择对象规则，参见<a href="../beginners/ht-beginners-guide.html#ref_filter">过滤器章节</a></li><li><code>appendSelection(datas)</code>追加选中对象，参数可为单个对象，也可为<code>ht.List</code>或<code>Array</code>数组，简写为<code>as</code></li><li><code>setSelection(datas)</code>设置选中对象，参数可为单个对象，也可为<code>ht.List</code>或<code>Array</code>数组，简写为<code>ss</code></li><li><code>removeSelection(datas)</code>取消选中对象，参数可为单个对象，也可为<code>ht.List</code>或<code>Array</code>数组，简写为<code>rs</code></li><li><code>clearSelection()</code>取消所有选中对象，简写为<code>cs</code></li><li><code>selectAll()</code>选中<code>DataModel</code>中所有对象，简写为<code>sa</code></li><li><code>size()</code>返回当前选中对象个数</li><li><code>isEmpty()</code>判断当前是否没有选中对象</li><li><code>contains(data)</code>判断<code>data</code>对象是否被选中，简写为<code>co</code></li><li><code>getFirstData()</code>返回首个被选中的对象，如果没有选中对象则返回空，简写为<code>fd</code></li><li><code>getLastData()</code>返回最后被选中的对象，如果没有选中对象则返回空，简写为<code>ld</code></li><li><code>each(function(data){}, scope)</code>遍历所有被选中对象</li><li><code>getSelection()</code>获取所有被选中对象数组，注意不可直接对返回数组进行增删操作。</li><li><code>toSelection(matchFunc, scope)</code>返回过滤后的选中对象，<code>matchFunc</code>为空时代表复制全部到新数组</li><li><code>addSelectionChangeListener(function(e){}, scope)</code>增加监听器，监听选中变化事件，简写为<code>ms</code>：<ul><li><code>e.datas</code>包含所有选中状态变化的对象，之前选中现在取消选中，或之前没选中现在被选中的对象</li><li><code>e.kind === &#39;set&#39;</code>代表此事件由<code>setSelection(datas)</code>引发</li><li><code>e.kind === &#39;remove&#39;</code>代表此事件由<code>removeSelection(datas)</code>引发</li><li><code>e.kind === &#39;append&#39;</code>代表此事件由<code>appendSelection(datas)</code>引发</li><li><code>e.kind === &#39;clear&#39;</code>代表此事件由<code>clearSelection(datas)</code>引发</li></ul></li><li><code>removeSelectionChangeListener(function(e){}, scope)</code>删除选中变化事件监听器，简写为<code>ums</code>：</li></ul>

<p><iframe src="examples/example_datamodel.html" style="height:130px"></iframe></p>

<pre><code>index = 0;
dataModel = new ht.DataModel();  
selectionModel = dataModel.getSelectionModel();                                           

// monitor data property change event
dataModel.addDataPropertyChangeListener(function(e){                    
    document.getElementById(&#39;property&#39;).innerText = e.data + &#39;\&#39;s &#39; + e.property + &#39; changed&#39;;
});

// monitor data model change event
dataModel.addDataModelChangeListener(function(e){
    var output;
    if(e.kind === &#39;add&#39;){
        output = e.data + &#39; added, &#39;;
    }
    else if(e.kind === &#39;remove&#39;){
        output = e.data + &#39; removed, &#39;;
    }
    else if(e.kind === &#39;clear&#39;){
        output = &#39;data model cleared, &#39;;
    }
    output += &#39;size:&#39; + dataModel.size();
    document.getElementById(&#39;model&#39;).innerText = output;
});

// monitor selection model change event
selectionModel.addSelectionChangeListener(function(e){
    var output = &#39;&#39;;
        size = selectionModel.size();                    
    if(size === 0){
        output = &#39;nothing selected&#39;;
    }
    else if(size === 1){
        output = selectionModel.getLastData() + &#39; selected&#39;;
    }
    else{
        output = size + &#39; datas selected&#39;;
    }
    document.getElementById(&#39;selection&#39;).innerText = output;
});

graphPane.getGraphView().setEditable(true);

addData();
addData();
selectionModel.setSelection(addData());

function addData(){
    var node = new ht.Node();                             
    node.setPosition(50 + index % 12 * 50, 50);
    node.setName(&#39;node&#39; + index++);    
    dataModel.add(node);                                               
    return node;
}
function removeData(){
    while(selectionModel.size() &gt; 0){
        dataModel.remove(selectionModel.getLastData());
    }
} </code></pre>    <hr id="contact" style="margin: 20px 0 1px 0">
    <a href="http://www.hightopo.com"><img class="logo"></a>欢迎交流 <a href="mailto:service@hightopo.com">service@hightopo.com</a>
</body>
</html>
